import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;


public class RISCServerTest {
	
	static final int DEFAULT_PORT = 2122;
	static ArrayList<RISC.Player> plyrs;
	static ArrayList<Socket> accepted;
	private static TreeSet<String> usernameList;
	private static HashSet<PrintWriter> broadcast;
	private static HashSet<ObjectOutputStream> obj_broadcast;
	static CyclicBarrier barrier, updateBarrier;
	static Random r = new Random();
	static RISC game;
	
	public static void main(String[] args) throws IOException {
		System.out.println("RISC server started!\n");
		ServerSocket listener = new ServerSocket(DEFAULT_PORT);
		usernameList = new TreeSet<String>();
		broadcast = new HashSet<PrintWriter>();
		obj_broadcast = new HashSet<ObjectOutputStream>();
		plyrs = new ArrayList<RISC.Player>();
		//accepted = new ArrayList<Socket>();
		
		try {
			game = new RISC(3);
			game.time = 1;
			barrier = new CyclicBarrier(game.max, new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(10000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					game.setupMap(plyrs);
					game.sendMap();
					System.out.println("Map updates sent");
					game.mapUpdate = true;
				}
			});
			updateBarrier = new CyclicBarrier(game.max, new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						System.out.println("Passing the update barrier (failure?): " + e.getMessage());
					}
					game.updateMap();
					game.sendMap();
				}
			});
			while(true) {
				new RISC.Player(listener.accept()).start();
			}
		} finally {
			listener.close();
		}
	}
	
	static class RISC {
		
		private int max = 0, turn = 0;
		private volatile int status = 0;
		private volatile boolean rdy4Next = false;
		private volatile boolean gameOver = false;
		private volatile static boolean mapUpdate = false;
		private static int time;
		static Map map;
		static ArrayList<Move> pMoves;
		static ArrayList<Attack> pAttacks;
		private volatile static boolean allConnected = false;
		private volatile static int setupDone = 0;
		
		public RISC(int max) {
			this.max = max;
			map = new Map();
		}
		
		public static void setupMap(ArrayList<RISC.Player> plyrs) {
			//if(!allConnected) {
				for(RISC.Player p : plyrs) {
					while(true) {
						int tryX, tryY;
						while(true) {
							tryX = (int)(Math.random() * ((10 - 0) + 1));
							tryY = 0 + (int)(Math.random() * ((10 - 0) + 1));//0 = min, max = 10
							if(tryX >= 0 && tryX < map.getRows())
								if(tryY >= 0 && tryY < map.getCols())
									break;
						}
						if(map.getTerritory(tryX, tryY).getOwner().equals("")) {
							map.getTerritory(tryX, tryY).setOwner(p.getUserName());
							map.getTerritory(tryX, tryY).SetAmount(5);
							map.getTerritory(tryX, tryY).SetUpdated(true);
							map.getTerritory(tryX, tryY).setColor(p.getColor());
							p.addTerritory(map.getTerritory(tryX, tryY));
							p.setAmount(5);
							break;
						}
					}
				}
			//}
		}
		
		public static void sendMap() {
			for(PrintWriter p : broadcast) {
				p.println("MU:Map en route");
			}
			for(ObjectOutputStream o : obj_broadcast) {
				try {
					o.writeObject(map);
					System.out.println("Sent map to object outputstream at: " + o.toString());
				} catch (IOException e) {
					System.out.println("Map send failure: " + e.getMessage() + "\n");
				}
			}
			for(RISC.Player p : plyrs) {
				p.sendPlayerInfo();
				System.out.println("Sent information to " + p.getName());
			}
		}
		
		public static void processOrders(ArrayList<Order> orders, Player p) {
			if(!p.finishedTurnProcessing()) {
				
			}
			else
				return;
		}
		
		public static void updateMap() {
			//process map updates;
			mapUpdate = true;
		}
		
		static class Player extends Thread {
			
			private Socket connection;
			private BufferedReader in;
			private PrintWriter out;
			private String name;
			private ObjectOutputStream obj_out;
			private static ObjectInputStream obj_in;
			private Color color;
			private int troops;
			private ArrayList<Territory> myHouse;
			private static ArrayList<Order> myOrders;
			private static List<Move> myMoves;
			private static List<Attack> myAttacks;
			private boolean lost = false;
			private boolean turnWorkDone = false;
			
			public Player(Socket socket) {
				connection = socket;
				System.out.println("new Player made");
			}
			
			public String getUserName() {
				return name;
			}
			
			public Color getColor() {
				return color;
			}
			
			public void setAmount(int n) {
				troops = n;
			}
			
			public boolean finishedTurnProcessing() {
				return turnWorkDone;
			}
			
			public void addTerritory(Territory... annexed) {
				for(Territory t : annexed) {
					myHouse.add(t);
				}
			}
			
			public void removeTerritory(Territory... stolen) {
				for(Territory lost : stolen) {
					myHouse.remove(lost);
				}
			}
			
			private static Thread turnRead = new Thread() {
				public void run() {
					while(true) {
						try {
							//myOrders = (ArrayList<Order>) obj_in.readObject();
							myMoves = (ArrayList<Move>) obj_in.readObject();
							myAttacks = (ArrayList<Attack>) obj_in.readObject();
						} catch (ClassNotFoundException | IOException e) {
							System.out.println("Server receive exception: " + e.getMessage());
							break;
						}
					}
				}
			};
			
			public void sendPlayerInfo() {
				out.println("PI:" + troops + "," + myHouse.size() + ";");
			}
			
			public void run() {
				try {
					System.out.println("Player run() started");
					in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
					System.out.println("in made");
					out = new PrintWriter(connection.getOutputStream(), true);
					System.out.println("out made");
					obj_out = new ObjectOutputStream(connection.getOutputStream());
					System.out.println("server obj out made");
					obj_in = new ObjectInputStream(connection.getInputStream());
					System.out.println("server obj in made");
					System.out.println("Connection from client accepted\n");
					synchronized(plyrs) {
						if(plyrs.size() > game.max) {
							//change this to start a new RISC server game on another RISC thread
							out.println("MSG:The game already has its set number of players! Please try connecting again later");
							try {
								connection.close();
							} catch(IOException e) {
								System.out.println("Server full, connection close: " + e.getMessage());
							}
						} else {
							plyrs.add(this);
							System.out.println("Players now: " + plyrs.size());
							out.println("AC:Accepted your client!");
							broadcast.add(out);
							obj_broadcast.add(obj_out);
							myHouse = new ArrayList<Territory>();
						}
					}							
					while(true) {
						name = in.readLine();
						if(name == null)
							return;
						synchronized (usernameList) {
							if(!usernameList.contains(name)) {
								usernameList.add(name);
								out.println("NA:Name accepted");
								break;
							}
						}
						out.println("NNA:Name not accepted");
						continue;
					}
					color = new Color(r.nextInt(256),r.nextInt(256),r.nextInt(256));
					out.println("CS:" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ";");
					System.out.println("Player " + name + " reached sleep, not enough players");
					while(plyrs.size() != game.max) {
						Thread.sleep(100L);
					}
					System.out.println("Player " + name + " reached awake, enough players");
					synchronized(game) {
						while(!allConnected) {
							if(plyrs.size() == game.max) {
								allConnected = true;
								break;
							} else
							Thread.sleep(100L);
						}
						
					}
					setupDone += 1;
					System.out.println(setupDone);
					System.out.println("setupDone after awakening " + setupDone);
					try {
						barrier.await();
					} catch (InterruptedException | BrokenBarrierException ex) {
						System.out.println("The latch for map updates broke: " + ex.getMessage());
					}
					System.out.println("Player " + name + " entering game loop");
					while(!game.gameOver) {
						while(!lost) {
							synchronized(game) {
								if(mapUpdate)
									mapUpdate = false;
							}
							out.println("ST:" + time);
							long start = System.currentTimeMillis();
							//turnRead.start();
							while(System.currentTimeMillis() - start <= 1000*(60*game.time + 2)) {
								try {
									try {
										myMoves = (List<Move>) obj_in.readObject();
										myAttacks = (List<Attack>) obj_in.readObject();
										System.out.println("Orders received");
										for(Order o : myMoves)
											System.out.println(o.toString());
										for(Order o : myAttacks)
											System.out.println(o.toString());
									} catch (ClassNotFoundException e) {
										System.out.println("Server received wrong type of order object" + e.getMessage());
										return;
									}
									Thread.sleep(50L);
								} catch (InterruptedException e) {
									//This is good
									System.out.println("Player " + name + " stopped reading for this turn");
									return;
								}
							}
							//turnRead.interrupt();
							synchronized(game) {
								processOrders(myOrders, this);
								myOrders.clear();
								turnWorkDone = true;
							}
							/*synchronized(game) {
								if(!mapUpdate) {
									updateMap();
									sendMap();
								}
							}*/
							try {
								updateBarrier.await();
							} catch (BrokenBarrierException e) {
								System.out.println("Player " + name + " broke the barrier (un/intentionally)?");
								System.out.println(e.getMessage());
								continue;
							}
						}
					}
					
				} catch (IOException e) {
					System.out.println("Player thread run loop failure: " + e.getMessage());
					e.printStackTrace();
				} catch (InterruptedException e) {
					System.out.println("Player " + name + " was interrupted!" + e.getMessage());
				} finally {
					/*try {
						connection.close();
					} catch (IOException e) {
						//Close handle case
					}*/
					//undo the above just to see what happens
				}
			}
		}
	}
}


/*import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;


public class RISCServer {
	
	static final int DEFAULT_PORT = 2122;
	static ArrayList<RISC.Player> plyrs;
	static ArrayList<Socket> accepted;
	private static TreeSet<String> usernameList;
	private static HashSet<PrintWriter> broadcast;
	private static HashSet<ObjectOutputStream> obj_broadcast;
	static Random r = new Random();
	static RISC game;
	
	public static void main(String[] args) throws IOException, InterruptedException {
		System.out.println("RISC server started!\n");
		ServerSocket listener = new ServerSocket(DEFAULT_PORT);
		usernameList = new TreeSet<String>();
		broadcast = new HashSet<PrintWriter>();
		obj_broadcast = new HashSet<ObjectOutputStream>();
		plyrs = new ArrayList<RISC.Player>();
		accepted = new ArrayList<Socket>();
		
		try {
			while(true) {
				game = new RISC(3);
				
				synchronized (plyrs) {
					while(!(plyrs.size() >= game.getMax())) {
						plyrs.add(new RISC.Player(listener.accept()));
						Thread.sleep(5000); //May allow synchronization to occur, stop messing with last connection
					}
					game.setupMap(plyrs);
					game.sendMap();
					game.setTurnTime(1);
				}
				
				for(RISC.Player p : plyrs) {
					p.start();
				}
			}
		} finally {
			listener.close();
		}
		
	}
	static class RISC {
		
		private int max, turn = 0;
		private volatile int status = 0;
		private volatile boolean rdy4Next = false;
		private volatile boolean gameOver = false;
		private int time;
		Map map;
		static ArrayList<Move> pMoves;
		static ArrayList<Attack> pAttacks;
		
		public RISC(int max) {
			this.max = max;
			map = new Map(10,10);
		}
		
		public int getMax() {
			return max;
		}
		
		public boolean isReadyForNextTurn() {
			return rdy4Next;
		}
		
		public void setReady(boolean ready) {
			rdy4Next = false;
		}
		
		public void nextTurn() {
			turn+=1;
		}
		
		public void setTurnTime(int time) {
			this.time = time;
		}
		
		public int getTurnTime() {
			return time;
		}
		
		public void setupMap(ArrayList<RISC.Player> plyrs) {
			for(RISC.Player p : plyrs) {
				while(true) {
					int tryX, tryY;
					while(true) {
						tryX = (int)(Math.random() * ((10 - 0) + 1));
						tryY = 0 + (int)(Math.random() * ((10 - 0) + 1));//0 = min, max = 10
						if(tryX >= 0 && tryX < map.getRows())
							if(tryY >= 0 && tryY < map.getCols())
								break;
					}
					if(map.getTerritory(tryX, tryY).getOwner().equals("")) {
						map.getTerritory(tryX, tryY).setOwner(p.getUserName());
						map.getTerritory(tryX, tryY).SetAmount(5);
						map.getTerritory(tryX, tryY).SetUpdated(true);
						map.getTerritory(tryX, tryY).setColor(p.getColor());
						p.addTerritory(map.getTerritory(tryX, tryY));
						p.setAmount(5);
						break;
					}
				}
			}
		}
		
		public void sendMapList() {
			String mapToString = "MU:";
			for(Territory t : map.updatedList()) {
				mapToString += t.toString();
			}
			for(PrintWriter p : broadcast) {
				p.println(mapToString);
			}
		}
		
		public void sendMap() {
			for(PrintWriter p : broadcast) {
				p.println("MU:Map en route");
			}
			for(ObjectOutputStream o : obj_broadcast) {
				try {
					o.writeObject(map);
				} catch (IOException e) {
					System.out.println("Map send failure: " + e.getMessage() + "\n");
				}
			}
			for(RISC.Player p : plyrs) {
				p.sendPlayerInfo();
			}
		}
		
		static class Player extends Thread {
			
			private Socket connection;
			private BufferedReader in;
			private PrintWriter out;
			private String name;
			private ObjectOutputStream obj_out;
			private static ObjectInputStream obj_in;
			private Color color;
			private int troops;
			private ArrayList<Territory> myHouse;
			
			
			public Player(Socket socket) throws IOException {
				connection = socket;
				in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
				out = new PrintWriter(connection.getOutputStream(), true);
				obj_out = new ObjectOutputStream(connection.getOutputStream());
				obj_in = new ObjectInputStream(connection.getInputStream());
				myHouse = new ArrayList<Territory>();
				broadcast.add(out);
				obj_broadcast.add(obj_out);
				
				setupName();
			}
			
			private void setupName() throws IOException {
				while(true) {
					name = in.readLine();
					if(name == null)
						return;
					synchronized (usernameList) {
						if(!usernameList.contains(name)) {
							usernameList.add(name);
							break;
						}
					}
					out.println("NNA:Name not accepted");
					continue;
				}
				out.println("NA:Name accepted");
				color = new Color(r.nextInt(256),r.nextInt(256),r.nextInt(256));
				out.println("CS:" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ";");
				return;
			}
			
			public String getUserName() {
				return name;
			}
			
			public Color getColor() {
				return color;
			}
			
			public void setAmount(int n) {
				troops = n;
			}
			
			public void addTerritory(Territory... annexed) {
				for(Territory t : annexed) {
					myHouse.add(t);
				}
			}
			
			public void removeTerritory(Territory... stolen) {
				for(Territory lost : stolen) {
					myHouse.remove(lost);
				}
			}
			
			public int getTroopCount() {
				return troops;
			}
			
			public int getAmtTerritory() {
				return myHouse.size();
			}
			
			public void sendPlayerInfo() {
				out.println("PI:" + troops + "," + myHouse.size() + ";");
			}
			
			private static Thread turnRead = new Thread() {
				public void run() {
					while(true) {
						try {
							ArrayList<Move> inMove = (ArrayList<Move>) obj_in.readObject();
							ArrayList<Attack> inAtk = (ArrayList<Attack>) obj_in.readObject();
							for(Move m : inMove)
								pMoves.add(m);
							for(Attack a : inAtk)
								pAttacks.add(a);
						} catch (ClassNotFoundException | IOException e) {
							System.out.println("Server receive exception: " + e.getMessage());
							break;
						}
					}
				}
			};
			
			public void run() {
				try {
					out.println("MSG:Welcome, the game is about to begin");
					while(!game.gameOver) {
						synchronized(game) {
							if(game.isReadyForNextTurn()) {
								game.setReady(false);
								game.nextTurn();
							}
						}
						while(!game.isReadyForNextTurn()) {
							out.println("ST:" + game.getTurnTime());
							long start = System.currentTimeMillis();
							turnRead.start();
							while(System.currentTimeMillis() - start <= 1000*game.time) {
								try {
									Thread.sleep(500L);
								} catch (InterruptedException e) {
									//This is good
								}
							}
							turnRead.interrupt();
						}
					}
				} finally {
					try {
						connection.close();
					} catch(IOException e) {
						System.out.println("Player quit error: " + e.getMessage());
					}
				}
			}
		}
	}
}

 * Timer turnTimer = new Timer();
			turnTimer.schedule(new TimerTask() {
				public void run() {
					//this is implemented wrong, threads don't work like this
					//change it to listen to arbitrary thread, recognize which client send the list
					for(Player p : clients) {
						try {
						ArrayList<Order> tempOrders = game.receiveOrders(p.getSocket());
						for(Order o : tempOrders)
							orders.add(o);
						} catch (ClassNotFoundException | IOException e) {
							System.out.println("TimerTask order receive failure " + e.getMessage() + "\n");
						} 
					}
				}
			}, time * 1000);
 


*/